#include "uwifi.h"
#include "html.h"

// Update these with values suitable for your network.
WiFiClient espClient;
PubSubClient mqtt_client(espClient);

int linkAllProcess = 0;
bool scanNetworkFlag = 0;

int linkNetworkCount = 0;
bool linkNetworkState = 0;

int linkMqttSerCount = 0;
bool linkMqttSerState = 0;

void (*callback)(char *, byte *, unsigned int);

config_info_t configInfo;
received_subcribe_t resub;
// String index_html = "<html><head><meta charset=\"UTF-8\"><meta name=\"viewport\"content=\"width=device-width, initial-scale=1.0\"><meta http-equiv=\"X-UA-Compatible\"content=\"ie=chrome\"><title>ESP32网页配置</title></head><body><form action=\"/HandleVal\"method=\"post\">WiFi名称：<input type=\"text\"name=\"stassid\"><br>WiFi密码：<input type=\"password\"name=\"stapawd\"><br>MQTT服务器地址：<input type=\"text\"name=\"mqttserver\"><br>MQTT服务器端口：<input type=\"text\"name=\"mqttport\"><br>MQTT用户：<input type=\"text\"name=\"mqttuser\"><br>MQTT密码：<input type=\"password\"name=\"mqttpawd\"><br><input type=\"submit\"value=\"连接\"></form></body></html>";

// 设置web服务器访问端口为80端口及server变量
WebServer server(80);

void setup_wifi(void)
{
  uint32_t chip_id;
  resub.finish = 0;
  WiFi.mode(WIFI_AP_STA); // 设置模式为AP+STA
  esp_efuse_mac_get_default((uint8_t *)&chip_id);
  configInfo.ap_ssid = String("ESP32-") + String(chip_id, HEX); // 设置ESP32发出的AP热点名称
  configInfo.ap_passwd = String("gxbdt2016");                   // 设置ESP32发出的AP热点密码
  WiFi.softAP(configInfo.ap_ssid, configInfo.ap_passwd);        // 启动ESP32AP热点
  DEBUG_LOG_println("====== AP setup OK ======");
  DEBUG_LOG_print("AP IPaddr: ");
  DEBUG_LOG_println(WiFi.softAPIP()); // 显示AP热点IP地址
  DEBUG_LOG_print("AP ssid  : ");
  DEBUG_LOG_println(WiFi.softAPSSID()); // 显示AP热点SSID
  DEBUG_LOG_print("AP passwd: ");
  DEBUG_LOG_println(configInfo.ap_passwd); // 显示AP热点密码

  if (MDNS.begin("esp32"))
  {
    DEBUG_LOG_println("MDNS responder started");
  }
  server.on("/", web_HandleIndex_Html);                     // 绑定根目录和访问函数，理解为访问根目录就相当于访问web_HandleIndex_Html函数
  server.on("/index.html", web_HandleIndex_Html);           // 绑定index目录和访问函数，理解为访问根目录就相当于访问web_HandleIndex_Html函数
  server.on("/config.html", web_HandleConfig_Html);
  server.on("/connect.html", web_HandleConnect_Html);
  server.on("/public.html", web_HandlePublic_Html);
  server.on("/subcribe.html", web_HandleSubcribe_Html);
  server.on("/config", HTTP_GET, web_HandleConfig);         // 接收到GET请求，执行web配置函数
  server.on("/connect", HTTP_GET, web_HandleConnect);       // 接收到GET请求，绑定连接数据函数
  server.on("/infomation", HTTP_GET, web_HandleInfomation); // 绑定wifi连接信息反馈函数
  server.on("/public", HTTP_GET, web_HandlePublic);         // 接收到GET请求，绑定消息发布函数
  server.on("/subcribe", HTTP_GET, web_HandleSubcribe);     // 接收到GET请求，绑定消息订阅函数
  server.on("/cancel", HTTP_GET, web_HandleCancelSub);      // 绑定取消消息订阅函数
  server.on("/content", HTTP_GET, web_HandleContext);       // 绑定消息订阅内容发聩函数
  server.onNotFound(web_HandleNotFound);                    // 请求失败回调函数

  server.begin(); // 开启服务器
  DEBUG_LOG_println("HTTP server started");

  callback = mqtt_Callback; // 传递MQTT的回调函数
  delay(100);
}

void loop_wifi(void)
{
  // DEBUG_LOG_print("WIFI:");
  // DEBUG_LOG_println(millis());
  wifi_Status();  // 查询wifi的连接状态，主要是想查到wifi断开后，做自动连接
  wifi_ScanAP();  // 查询附近的wifi热点
  wifi_Connect(); // 这个函数仅判断是否连接到wifi，没连接上则每秒查看一下，20次确认没连上则停止，否则打印连接ip信息
  mqtt_Connect(); // 尝试连接到mqtt服务器，3次确认没连上则停止
  mqtt_Loop();    // 如果连接上MQTT服务器，循环查询这个函数，保持连接
  web_ShowHtml(); // 显示ESP32本机的web页面，一般是做网络配置使用
}

void web_ShowHtml(void)
{
  server.handleClient(); // 显示网页使用，如果停掉，网页这不显示
  // MDNS.update();
}

void web_HandleIndex_Html(void)
{
  server.send(200, "text/html", index_html); // 展示配网页面
}

void web_HandleConfig_Html(void)
{
  server.send(200, "text/html", config_html); // 展示配网页面
}

void web_HandleConnect_Html(void)
{
  server.send(200, "text/html", connect_html); // 展示连接页面
}

void web_HandlePublic_Html(void)
{
  server.send(200, "text/html", public_html); // 展示发布页面
}

void web_HandleSubcribe_Html(void)
{
  server.send(200, "text/html", subcribe_html); // 展示订阅页面
}

void web_HandleConfig(void)
{
  String content;
  File newFile;
  // 从POST请求获取以下信息，取得信息没问题说明网页配置成功
  configInfo.sta_ssid = server.arg("stassid");
  configInfo.sta_passwd = server.arg("stapawd");
  configInfo.mqtt_addr = server.arg("mqttaddr");
  configInfo.mqtt_port = server.arg("mqttport");
  configInfo.mqtt_user = server.arg("mqttuser");
  configInfo.mqtt_passwd = server.arg("mqttpawd");
  DEBUG_LOG_println(configInfo.sta_ssid);
  DEBUG_LOG_println(configInfo.sta_passwd);
  DEBUG_LOG_println(configInfo.mqtt_addr);
  DEBUG_LOG_println(configInfo.mqtt_port);
  DEBUG_LOG_println(configInfo.mqtt_user);
  DEBUG_LOG_println(configInfo.mqtt_passwd);
  WiFi.begin(configInfo.sta_ssid, configInfo.sta_passwd); // 连接wifif
  linkAllProcess = 1;                                     // 连接步骤，下次要执行步骤1功能
  DEBUG_LOG_println("Web WiFi config succed.");
  DEBUG_LOG_println("");
  DEBUG_LOG_println("WiFi_config writing to LittleFS");
  newFile = LittleFS.open(wifi_info, "w+"); // 将配网信息保存在ESP32的Flash中，这里采用LittleFS文件系统
  content = "1.";
  content = content + String("\"") + configInfo.sta_ssid + String("\",");
  content = content + String("\"") + configInfo.sta_passwd + String("\",");
  content = content + String("\"") + configInfo.mqtt_addr + String("\",");
  content = content + String("\"") + configInfo.mqtt_port + String("\",");
  content = content + String("\"") + configInfo.mqtt_user + String("\",");
  content = content + String("\"") + configInfo.mqtt_passwd + String("\"");
  newFile.printf(content.c_str());
  newFile.close();
  DEBUG_LOG_println("WiFi_config writing done.");
  DEBUG_LOG_println("");
}

void web_HandleConnect(void) // 获取一键重连
{
  WiFi.begin(configInfo.sta_ssid, configInfo.sta_passwd);
  linkAllProcess = 1;
}

void web_HandleInfomation(void) // 更新连接状态信息
{
  String info = WiFi.localIP().toString();
  if (WiFi.status() == WL_CONNECTED)
  {
    if (mqtt_client.state() == MQTT_CONNECTED)
    {

      info = info + String(",connected,connected,") + String(millis());
    }
    else
    {
      info = info + String(",connected,disconnected,") + String(millis());
    }
  }
  else
  {
    info = info + String(",disconnected,disconnected,") + String(millis());
  }
  server.send(200, "text/plane", info);
}

void web_HandlePublic(void) // 获取发布主题和内容
{
  static uint8_t pubCount = 1;
  String pubTopic = server.arg("pubtopic");
  String pubContent = server.arg("pubcontext");
  if (mqtt_client.publish(pubTopic.c_str(), pubContent.c_str()) == false)
  {
    DEBUG_LOG_println("Public mqtt send failed.");
    server.send(200, "text/plane", String("发布消息失败 ") + String(pubCount));
  }
  else
  {
    DEBUG_LOG_print("MQTT-Pub[");
    DEBUG_LOG_print(pubTopic);
    DEBUG_LOG_print("]:");
    DEBUG_LOG_println(pubContent);
    server.send(200, "text/plane", String("发布消息成功 ") + String(pubCount));
  }
  pubCount++;
}

void web_HandleSubcribe(void) // 获取订阅主题
{
  static uint8_t subCount = 1;
  String subTopic = server.arg("subtopic");
  if (mqtt_client.subscribe(subTopic.c_str()) == false)
  {
    DEBUG_LOG_println("Subscribe mqtt failed.");
    server.send(200, "text/plane", String("订阅主题失败 ") + String(subCount));
  }
  else
  {
    DEBUG_LOG_print("Sub-topic[");
    DEBUG_LOG_print(subTopic);
    DEBUG_LOG_println("]");
    server.send(200, "text/plane", String("订阅主题成功 ") + String(subCount));
  }
  subCount++;
}

void web_HandleCancelSub(void) // 取消订阅主题
{
  static uint8_t canCount = 1;
  String canTopic = server.arg("cantopic");
  if (mqtt_client.unsubscribe(canTopic.c_str()) == false)
  {
    DEBUG_LOG_println("Unsubscribe mqtt failed.");
    server.send(200, "text/plane", String("取消订阅失败 ") + String(canCount));
  }
  else
  {
    DEBUG_LOG_print("Unsub-topic[");
    DEBUG_LOG_print(canTopic);
    DEBUG_LOG_println("]");
    server.send(200, "text/plane", String("取消订阅成功 ") + String(canCount));
  }
  canCount++;
}

void web_HandleContext(void) // 更新订阅内容
{
  if (resub.finish)
  {
    static uint8_t conCount = 1;
    String Content = String("[") + resub.topics + String("]:") + resub.content + String(" ") + String(conCount);
    resub.finish = 0;
    server.send(200, "text/plane", Content);
    conCount++;
  }
}

void web_HandleNotFound(void)
{
  // 错误信息页面
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++)
  {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void wifi_Status(void)
{
  // 如果wifi状态发生改变，可以是从连接变化成断开状态或者从断开变化到连接状态，都会进来这里
  if (WiFi.isConnected() != linkNetworkState)
  {
    linkNetworkState = WiFi.isConnected();
    if (linkNetworkState)
    {
      arrayTime[0].intervalTick = 500; // 已连接上WiFi，未连接MQTT服务器，LED每2500ms亮一次
    }
    else
    {
      linkAllProcess = 0;
      arrayTime[0].intervalTick = 1000; // 未连接上WiFi，LED每1000ms亮一次
      if ((!configInfo.sta_ssid.equals("")) && (!configInfo.sta_passwd.equals("")))
      {
        WiFi.begin(configInfo.sta_ssid, configInfo.sta_passwd); // 自动重连wifi
        linkAllProcess = 1;                                     // 连接步骤，下次要执行步骤1功能
      }
    }
  }
}

void wifi_ScanAP(void)
{
  if (scanNetworkFlag)
  {
    int numberOfWifi = WiFi.scanComplete();
    switch (numberOfWifi) // 判断wifi热点扫描情况
    {
    case -1:
      DEBUG_LOG_println("WiFi scaning..."); // 还在扫描wifi热点
      break;
    case -2:
      DEBUG_LOG_println("No trigger wifi scan"); // 没有启用扫描wifi热点
      scanNetworkFlag = 0;
      break;
    case 0:
      DEBUG_LOG_println("No wifi found."); // 扫描结束，没有找到wifi热点
      WiFi.scanDelete();                   // 删除扫描到的信息
      scanNetworkFlag = 0;
      break;
    default:
      for (int i = 0; i < numberOfWifi; i++) // 扫描结束，取出wifi热点信息
      {
        DEBUG_LOG_print(i + 1);
        DEBUG_LOG_print(": ");
        DEBUG_LOG_print(WiFi.SSID(i));
        DEBUG_LOG_print("|[");
        DEBUG_LOG_print(WiFi.RSSI(i));
        DEBUG_LOG_print("]|");
        switch (WiFi.encryptionType(i))
        {
        case WIFI_AUTH_OPEN:
          DEBUG_LOG_println("AUTH_OPEN"); // 开放认证，没有密码
          break;
        case WIFI_AUTH_WEP:
          DEBUG_LOG_println("AUTH_WEP"); // WEP认证，有密码
          break;
        case WIFI_AUTH_WPA_PSK:
          DEBUG_LOG_println("AUTH_WPA_PSK"); // WEP_PSK认证，有密码
          break;
        case WIFI_AUTH_WPA2_PSK:
          DEBUG_LOG_println("AUTH_WPA2_PSK"); // WPA2_PSK认证，有密码
          break;
        case WIFI_AUTH_WPA_WPA2_PSK:
          DEBUG_LOG_println("AUTH_WPA_WPA2_PSK"); // WPA_WPA2_PSK认证
          break;
        case WIFI_AUTH_ENTERPRISE:
          DEBUG_LOG_println("AUTH_EAP"); // EAP认证，有密码
          break;
        case WIFI_AUTH_WPA3_PSK:
          DEBUG_LOG_println("AUTH_WPA3_PSK"); // WPA3_PSK认证，有密码
          break;
        case WIFI_AUTH_WPA2_WPA3_PSK:
          DEBUG_LOG_println("AUTH_WPA2_WPA3_PSK"); // WPA2_WPA3认证，有密码
          break;
        case WIFI_AUTH_WAPI_PSK:
          DEBUG_LOG_println("AUTH_WAPI_PSK"); // WAPI_PSK认证，有密码
          break;
        case WIFI_AUTH_WPA3_ENT_192:
          DEBUG_LOG_println("AUTH_WPA3_ENT_192"); // WPA3_ENT_192认证，有密码
          break;
        default:
          DEBUG_LOG_println("AUTH_ERROR");
          break;
        }
      }
      WiFi.scanDelete(); // 删除扫描到的信息
      scanNetworkFlag = 0;
      break;
    }
  }
}

void wifi_Connect(void)
{
  if (linkAllProcess == 1) // 这里是连接步骤1
  {
    linkNetworkCount++; // 计数wifi连接次数
    if (WiFi.status() == WL_CONNECTED)
    {
      randomSeed(micros());
      DEBUG_LOG_println("");
      DEBUG_LOG_println("====== WiFi connected ======");
      DEBUG_LOG_print(" IP address : ");
      DEBUG_LOG_println(WiFi.localIP());
      DEBUG_LOG_print(" Subnet mask: ");
      DEBUG_LOG_println(WiFi.subnetMask());
      DEBUG_LOG_print(" Gateway IP : ");
      DEBUG_LOG_println(WiFi.gatewayIP());
      // 读取到MQTT服务器和端口号不为空，则准备进度连接步骤2，为空则不在进行MQTT连接
      if ((!configInfo.mqtt_addr.equals("")) && (!configInfo.mqtt_port.equals("")))
      {
        mqtt_client.setServer(configInfo.mqtt_addr.c_str(), configInfo.mqtt_port.toInt()); // 配置MQTT服务器信息，IP地址和访问端口
        mqtt_client.setCallback(callback);                                                 // 设置MQTT消息回调函数
        linkAllProcess = 2;                                                                // 设置连接步骤2
        linkMqttSerCount = 0;
      }
      else
      {
        linkAllProcess = 0;
      }
      linkNetworkState = 1;
      linkNetworkCount = 0;
    }
    else if (linkNetworkCount < linkNetworkMax)
    {
      DEBUG_LOG_println("Try connecting ...");
    }
    else
    {
      // 连接次数大于上限，停止连接
      DEBUG_LOG_println("[Waring] WiFi connect failed.");
      linkAllProcess = 0;
      linkNetworkCount = 0;
    }
  }
}

void mqtt_Connect(void)
{
  if (linkAllProcess == 2) // 这里是连接步骤2
  {
    if (linkMqttSerCount == 0)
    {
      DEBUG_LOG_println(""); // 计数MQTT连接次数，三次连接不成功则失败
      DEBUG_LOG_println("====== Mqtt connect ======");
    }
    linkMqttSerCount++;
    if (!mqtt_client.connected()) // 判断是否连接上MQTT服务器
    {
      uint32_t chip_id;
      esp_efuse_mac_get_default((uint8_t *)&chip_id);
      DEBUG_LOG_print("Attempting MQTT connection...");
      String clientId = "ESP32Client-" + String(chip_id, HEX);
      if (mqtt_client.connect(clientId.c_str(), configInfo.mqtt_user.c_str(), configInfo.mqtt_passwd.c_str())) // 这里尝试连接MQTT服务器，包含设备ID，用户名，密码
      {
        DEBUG_LOG_println("connected");
        DEBUG_LOG_println("");
        linkAllProcess = 3; // 连接上MQTT服务器后，连接步骤要设置到3
        linkMqttSerCount = 0;
      }
      else
      {
        if (linkMqttSerCount < linkMqttSerMax)
        {
          DEBUG_LOG_print("failed, ErrorCode=");
          DEBUG_LOG_println(mqtt_client.state());
        }
        else
        {
          DEBUG_LOG_println("[Waring] MQTT connect failed."); // 超过连接上限，连接失败
          DEBUG_LOG_println("");
          linkAllProcess = 0;
          linkMqttSerCount = 0;
        }
      }
    }
  }
}

void mqtt_Status(void)
{
  if (mqtt_client.state() != linkMqttSerState)
  {
    linkMqttSerState = mqtt_client.state(); // 判断ESP32与MQTT服务器连接状态
    if (linkMqttSerState != MQTT_CONNECTED)
    {
      linkAllProcess = 0;
      if (WiFi.isConnected())
      {
        arrayTime[0].intervalTick = 500; // 已连接上WiFi，未连接MQTT服务器，LED每500ms亮一次
        DEBUG_LOG_println("[Waring] MQTT connect failed.");
        if ((!configInfo.mqtt_addr.equals("")) && (!configInfo.mqtt_port.equals("")))
        {
          mqtt_client.setServer(configInfo.mqtt_addr.c_str(), configInfo.mqtt_port.toInt());
          mqtt_client.setCallback(callback);
          linkAllProcess = 2;
        }
      }
      else
      {
        arrayTime[0].intervalTick = 1000; // wifi断开了，LED每1000ms亮一次
      }
    }
    else
    {
      arrayTime[0].intervalTick = 250; // 已连接上WiFi并已连接MQTT服务器，LED每250ms亮一次
    }
  }
}

void mqtt_Loop(void)
{
  if (linkAllProcess == 3) // 连接步骤3
  {
    mqtt_client.loop(); // 连接上MQTT服务器这里要不断循环，否则连接会断开
  }
}

void mqtt_Callback(char *topic, byte *payload, unsigned int length)
{
  uint8_t tmpBuff[200];
  memcpy(tmpBuff, payload, length);
  tmpBuff[length] = 0;
  resub.finish = 1;
  resub.topics = String(topic);
  resub.content = String((char *)tmpBuff);
  DEBUG_LOG_print("MQTT-Sub["); // 这里是MQTT消息订阅回调函数，主要是收到对应的主题后，会发送对应的数据给串口
  DEBUG_LOG_print(resub.topics);
  DEBUG_LOG_print("]:");
  DEBUG_LOG_print(resub.content);
  DEBUG_LOG_println("");
}
